using Sidvall.Data;
using System.Collections.Generic;
using System.Threading.Tasks;

namespace Sidvall.Business
{
    public sealed class DataPortalContext<TResult, TContext> : IDataPortalContext, IDataPortalContextResult<TResult>
    {
        #region Public Members

        public string Resource { get; private set; }
        public string Action { get; private set; }
        public long? TotalRowCount { get; set; }
        public Sidvall.Data.AcceptChangesSettings AcceptChangesSettings { get; set; }
        public Sidvall.Business.DataPortalResult Result { get; set; }
        public TContext OperationContext { get; set; }
        public string StrategyContext { get; set; }
        public bool ResultValueIsNewObject { get; set; }
        public object TaskContext { get; set; }
        public long RetryCount { get; private set; }
        public bool CopyOnSave { get; set; } = true;

        #region AffectedResources

        private Sidvall.Security.ResourceItemCollection _AffectedResources;
        public Sidvall.Security.ResourceItemCollection AffectedResources
        {
            get
            {
                if (_AffectedResources == null)
                    _AffectedResources = new Sidvall.Security.ResourceItemCollection();
                return _AffectedResources;
            }
        }

        #endregion
        #region CriteriaId

        public string CriteriaId
        {
            get
            {
                if ((this.Criteria != null) && (this.Criteria.CriteriaId != null))
                    return this.Criteria.CriteriaId;
                return string.Empty;
            }
        }

        #endregion
        #region Exception

        private System.Exception _Exception;
        public System.Exception Exception
        {
            get
            {
                return _Exception;
            }
            set
            {
                _Exception = value;
                if (_Exception != null)
                    this.Result = DataPortalResult.Failed;
            }
        }

        #endregion
        #region MaxRetryCount

        private long _MaxRetryCount;
        public long MaxRetryCount
        {
            get
            {
                return _MaxRetryCount;
            }
            set
            {
                if (value < 0)
                    value = 0;
                _MaxRetryCount = value;
            }
        }

        #endregion
        #region ObjectParameters

        private System.Lazy<ObjectParameterCollection> _ObjectParameters = new System.Lazy<ObjectParameterCollection>();
        public ObjectParameterCollection ObjectParameters
        {
            get
            {
                return _ObjectParameters.Value;
            }
        }

        #endregion
        #region Retry

        public bool Retry
        {
            get
            {
                if (this.RetryCount >= this.MaxRetryCount)
                    return false;
                if (this.Result != DataPortalResult.Failed)
                    return false;
                return true;
            }
        }

        #endregion
        #region Parameters

        public ParameterCollection Parameters
        {
            get
            {
                if (this.ParameterProvider != null)
                    return this.ParameterProvider.Parameters;
                return null;
            }
        }

        #endregion
        #region ResultValueItem

        public TResult ResultValueItem
        {
            get
            {
                if (this.ResultValue is TResult)
                    return (TResult)this.ResultValue;
                return default(TResult);
            }
            set
            {
                this.ResultValue = value;
            }
        }

        #endregion
        #region ResultValueList

        public IEnumerable<TResult> ResultValueList
        {
            get
            {
                IEnumerable<TResult> items;

                items = this.ResultValue as IEnumerable<TResult>;
                if (items != null)
                    return items;
                if (this.ResultValueListProvider != null)
                    return this.ResultValueListProvider.DataItems;
                return null;
            }
            set
            {
                this.ResultValue = value;
            }
        }

        #endregion
        #region ResultValueListProvider

        public IDataRowProvider<TResult> ResultValueListProvider
        {
            get
            {
                return this.ResultValue as IDataRowProvider<TResult>;
            }
            set
            {
                this.ResultValue = value;
            }
        }

        #endregion
        #region SaveContext

        public ISaveContext SaveContext
        {
            get
            {
                return this.OperationContext as ISaveContext;
            }
        }

        #endregion
        #region Tasks

        private System.Collections.ObjectModel.Collection<Task> _Tasks;
        public System.Collections.ObjectModel.Collection<Task> Tasks
        {
            get
            {
                if (_Tasks == null)
                    _Tasks = new System.Collections.ObjectModel.Collection<Task>();
                return _Tasks;
            }
        }

        #endregion
        #region SortItemsProvider

        public ISortItemsProvider SortItemsProvider
        {
            get
            {
                return this.OperationContext as ISortItemsProvider;
            }
        }

        #endregion
        #region RelationItemsProvider

        public IRelationItemsProvider RelationItemsProvider
        {
            get
            {
                return this.OperationContext as IRelationItemsProvider;
            }
        }

        #endregion

        #region Copy

        public DataPortalContext<TResult, TContext> Copy(bool copyResult)
        {
            var item = new DataPortalContext<TResult, TContext>(this.Resource, this.Action, this.OperationContext)
            {
                StrategyContext = this.StrategyContext,
            };
            if (copyResult)
            {
                item.Result = this.Result;
                item.ResultValue = this.ResultValue;
                item.TotalRowCount = this.TotalRowCount;
                item.Exception = this.Exception;
            }
            return item;
        }

        #endregion
        #region DoRetry

        public bool DoRetry()
        {
            if (this.Retry)
            {
                this.Exception = null;
                this.ResultValue = null;
                this.Result = DataPortalResult.None;
                this.RetryCount += 1;
                return true;
            }
            this.MaxRetryCount = 0;
            this.RetryCount = 0;
            return false;
        }

        #endregion
        #region DoTaskAsync

        public async Task DoTaskAsync()
        {
            await DoTaskAsync(true).ConfigureAwait(false);
        }
        public async Task DoTaskAsync(bool throwException)
        {
            try
            {
                foreach (var task in this.Tasks)
                {
                    if (task != null)
                        await task.ConfigureAwait(false);
                }
            }
            finally
            {
                this.Tasks.Clear();
            }
            if (throwException)
            {
                if (this.Exception != null)
                    throw this.Exception;
            }
        }

        #endregion
        #region GetDataRowState

        public DataRowState? GetDataRowState()
        {
            return GetDataRowState(this.ResourceItem);
        }
        public DataRowState? GetDataRowState(IDataRowContextProvider provider)
        {
            if (provider == null)
                return null;
            if (this.SaveContext == null)
                return null;
            return SaveSettings.GetDataRowState(this.SaveContext.Settings, provider);
        }

        #endregion
        #region GetDataRowStateResolved

        public DataRowState? GetDataRowStateResolved()
        {
            return GetDataRowStateResolved(this.ResourceItem);
        }
        public DataRowState? GetDataRowStateResolved(IDataRowContextProvider provider)
        {
            if (this.SaveContext == null)
                return null;
            if (provider == null)
                return null;
            return SaveSettings.GetDataRowStateResolved(this.SaveContext.Settings, provider);
        }

        #endregion
        #region GetResultValueListProviderResolved

        public IDataRowProvider GetResultValueListProviderResolved()
        {
            var item = this.ResultValue as IDataRowProvider;
            if (item != null)
                return item;
            if (this.ResultValueList != null)
                return new DataRowProvider<TResult>(this.ResultValueList);
            return null;
        }

        #endregion
        #region InitializeAffectedResources

        public void InitializeAffectedResources(IEnumerable<Sidvall.Security.ResourceItem> items)
        {
            string resourceItemId;

            resourceItemId = null;
            var dataRowState = GetDataRowStateResolved();
            if (dataRowState != null)
            {
                switch (dataRowState.Value)
                {
                    case Sidvall.Data.DataRowState.Deleted:
                    case Sidvall.Data.DataRowState.Modified:
                        resourceItemId = this.ResourceItem.DataRowContextStorageKey;
                        break;
                    default:
                        break;
                }
            }
            this.AffectedResources.Clear();
            this.AffectedResources.Add(this.Resource, this.Action, this.CriteriaId, resourceItemId);
            this.AffectedResources.AddRange(items);
        }

        #endregion
        #region DataRowStateIsDeleted

        public bool? DataRowStateIsDeleted()
        {
            return DataRowStateIsDeleted(this.ResourceItem);
        }
        public bool? DataRowStateIsDeleted(IDataRowContextProvider provider)
        {
            var dataRowState = GetDataRowState(provider);
            if (dataRowState == null)
                return null;
            if (dataRowState.Value == DataRowState.Deleted)
                return true;
            return false;
        }

        #endregion
        #region DataRowStateIsDirty

        public bool? DataRowStateIsDirty()
        {
            return DataRowStateIsDirty(this.ResourceItem);
        }
        public bool? DataRowStateIsDirty(IDataRowContextProvider provider)
        {
            var dataRowState = GetDataRowStateResolved(provider);
            if (dataRowState == null)
                return null;
            if (dataRowState.Value != DataRowState.Unchanged)
                return true;
            return false;
        }

        #endregion
        #region DataRowStateIsSelfDirty

        public bool? DataRowStateIsSelfDirty()
        {
            return DataRowStateIsSelfDirty(this.ResourceItem);
        }
        public bool? DataRowStateIsSelfDirty(IDataRowContextProvider provider)
        {
            var dataRowState = GetDataRowState(provider);
            if (dataRowState == null)
                return null;
            if (dataRowState.Value != DataRowState.Unchanged)
                return true;
            return false;
        }

        #endregion
        #region DataRowStateIsNew

        public bool? DataRowStateIsNew()
        {
            return DataRowStateIsNew(this.ResourceItem);
        }
        public bool? DataRowStateIsNew(IDataRowContextProvider provider)
        {
            var dataRowState = GetDataRowState(provider);
            if (dataRowState == null)
                return null;
            if (dataRowState.Value == DataRowState.Added)
                return true;
            return false;
        }

        #endregion
        #region DataRowStateIsUnchanged

        public bool? DataRowStateIsUnchanged()
        {
            return DataRowStateIsUnchanged(this.ResourceItem);
        }
        public bool? DataRowStateIsUnchanged(IDataRowContextProvider provider)
        {
            var dataRowState = GetDataRowStateResolved(provider);
            if (dataRowState == null)
                return null;
            if (dataRowState.Value == DataRowState.Unchanged)
                return true;
            return false;
        }

        #endregion
        #region ToString

        public override string ToString()
        {
            return DataPortalManager.GetDescription(this);
        }

        #endregion
        #region IDataPortalContext Members

        IDataPortalContext IDataPortalContext.Copy(bool copyResult)
        {
            return Copy(copyResult);
        }
        string IDataPortalContext.ResourceItemId
        {
            get
            {
                if (this.ResourceItem != null)
                    return this.ResourceItem.DataRowContextStorageKey;
                return null;
            }
        }
        object IDataPortalContext.ResultValueItem
        {
            get
            {
                return this.ResultValueItem;
            }
            set
            {
                this.ResultValue = value;
            }
        }
        System.Collections.IEnumerable IDataPortalContext.ResultValueList
        {
            get
            {
                return this.ResultValueList;
            }
            set
            {
                this.ResultValue = value;
            }
        }
        IDataRowProvider IDataPortalContext.ResultValueListProvider
        {
            get
            {
                return this.ResultValue as IDataRowProvider;
            }
            set
            {
                this.ResultValue = value;
            }
        }
        IDataRowContextProvider IDataPortalContext.ResourceItem
        {
            get
            {
                return this.ResourceItem;
            }
        }
        IEnumerable<IDataRowContextProvider> IDataPortalContext.ResourceItems
        {
            get
            {
                return this.ResourceItems;
            }
        }
        ISaveContext IDataPortalContext.SaveContext
        {
            get
            {
                return this.SaveContext;
            }
        }

        #endregion

        #endregion
        #region Private Members

        internal object ResultValue { get; set; }

        #region Criteria

        private ICriteria Criteria
        {
            get
            {
                return this.OperationContext as ICriteria;
            }
        }

        #endregion
        #region ParameterProvider

        private IParameterProvider ParameterProvider
        {
            get
            {
                return this.OperationContext as IParameterProvider;
            }
        }

        #endregion
        #region ResourceItem

        private IDataRowContextProvider ResourceItem
        {
            get
            {
                if (this.SaveContext != null)
                    return this.SaveContext.Item as IDataRowContextProvider;
                return null;
            }
        }

        #endregion
        #region ResourceItems

        private IEnumerable<IDataRowContextProvider> ResourceItems
        {
            get
            {
                IEnumerable<IDataRowContextProvider> items;
                IDataRowProvider<IDataRowContextProvider> dataRowProvider;

                if (this.SaveContext != null)
                {
                    items = this.SaveContext.Item as IEnumerable<IDataRowContextProvider>;
                    if (items != null)
                        return items;
                    dataRowProvider = this.SaveContext.Item as IDataRowProvider<IDataRowContextProvider>;
                    if (dataRowProvider != null)
                        return dataRowProvider.DataItems;
                }
                return null;
            }
        }

        #endregion

        #endregion
        #region Constructors

        public DataPortalContext(string resource, string action, TContext operationContext)
        {
            this.Resource = resource;
            this.Action = action;
            this.OperationContext = operationContext;
        }
        private DataPortalContext()
        {
        }

        #endregion
    }
}
